﻿using ScottPlot;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;

namespace UtilZ.Dotnet.ScottPlotEx
{
    /// <summary>
    /// 矩形图
    /// </summary>
    public class RectanglePlot : IPlottable
    {
        private List<RectangleMarker> _rectangleMarkerList = null;
        private SKPaint _paint = null;

        private PlotLastDrawTarget _lastDrawTarget = PlotLastDrawTarget.Bitmap2;
        private SKBitmap _bitmap1 = null;
        private SKCanvas _bitmap1Canvas = null;

        private SKBitmap _bitmap2 = null;
        private SKCanvas _bitmap2Canvas = null;

        private CoordinateRect _alignedExtent;

        private PlotMoveDirection _moveOrientation = PlotMoveDirection.Down;
        /// <summary>
        /// 移动方向
        /// </summary>
        public PlotMoveDirection MoveOrientation
        {
            get { return this._moveOrientation; }
        }

        private RectanglePlotOptions _options = null;
        /// <summary>
        /// 可选项参数
        /// </summary>
        public RectanglePlotOptions Options
        {
            get { return this._options; }
        }


        /// <summary>
        /// 获取水平或垂直方向上移动步进像素值回调,不能为null
        /// </summary>
        private Func<int> _getStepFunc = null;

        /// <summary>
        /// 构造函数初始化
        /// </summary>
        public RectanglePlot()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        ~RectanglePlot()
        {
            this._paint?.Dispose();
            this._bitmap1?.Dispose();
            this._bitmap1Canvas?.Dispose();
            this._bitmap2?.Dispose();
            this._bitmap2Canvas?.Dispose();
        }


        /// <summary>
        /// 设置瀑布图
        /// </summary>
        /// <param name="dataWidth">数据宽带(移动方向的垂直方向时此值为FFT点数,否则为帧数)</param>
        /// <param name="dataHeight">数据高度(移动方向的水平方向时此值为FFT点数,否则为帧数)</param>
        /// <param name="moveOrientation">瀑布图移动方向</param>
        /// <param name="options">可选项参数</param>
        /// <param name="getStepFunc">获取水平或垂直方向上移动步进像素值回调,为null时不移动</param>
        /// <exception cref="ArgumentOutOfRangeException">参数无效异常</exception>
        /// <exception cref="ArgumentException">参数无效异常</exception>
        public void Set(int dataWidth, int dataHeight, PlotMoveDirection moveOrientation, RectanglePlotOptions options, Func<int> getStepFunc)
        {
            if (dataWidth < PlotConstant.SIZE_MIN)
            {
                throw new ArgumentOutOfRangeException(nameof(dataWidth), $"数据宽度不能小于{PlotConstant.SIZE_MIN}");
            }

            if (dataHeight < PlotConstant.SIZE_MIN)
            {
                throw new ArgumentOutOfRangeException(nameof(dataHeight), $"数据高度不能小于{PlotConstant.SIZE_MIN}");
            }

            if (moveOrientation != PlotMoveDirection.Left &&
                moveOrientation != PlotMoveDirection.Right &&
                moveOrientation != PlotMoveDirection.Up &&
                moveOrientation != PlotMoveDirection.Down &&
                moveOrientation != PlotMoveDirection.None)
            {
                throw new ArgumentException("移动方向只能为上下左右", nameof(moveOrientation));
            }

            if (options == null)
            {
                options = RectanglePlotOptions.Default;
            }

            if (moveOrientation != PlotMoveDirection.None && getStepFunc == null)
            {
                throw new ArgumentNullException("当移动方向为\"移动\"时,获取水平或垂直方向上移动步进像素值回调不能为null.", nameof(getStepFunc));
            }

            //矩形标注
            this._paint?.Dispose();
            this._bitmap1?.Dispose();
            this._bitmap1Canvas?.Dispose();
            this._bitmap2?.Dispose();
            this._bitmap2Canvas?.Dispose();

            if (options.Enable)
            {
                this._paint = new SKPaint()
                {
                    FilterQuality = options.Smooth ? SKFilterQuality.High : SKFilterQuality.None,
                    StrokeWidth = options.StrokeWidth,
                    Color = options.StrokeColor,
                    Style = SKPaintStyle.Stroke,
                };

                this._bitmap1 = new SKBitmap(dataWidth, dataHeight);
                this._bitmap1Canvas = new SKCanvas(this._bitmap1);

                if (moveOrientation != PlotMoveDirection.None)
                {
                    this._bitmap2 = new SKBitmap(dataWidth, dataHeight);
                    this._bitmap2Canvas = new SKCanvas(this._bitmap2);

                    this._lastDrawTarget = PlotLastDrawTarget.Bitmap2;
                }
                else
                {
                    if (this._rectangleMarkerList == null)
                    {
                        this._rectangleMarkerList = new List<RectangleMarker>();
                    }
                    else
                    {
                        this._rectangleMarkerList.Clear();
                    }
                }
            }
            else
            {
                this._paint = null;
                this._bitmap1 = null;
                this._bitmap1Canvas = null;
                this._bitmap2 = null;
                this._bitmap2Canvas = null;
                this._rectangleMarkerList?.Clear();
            }

            this._alignedExtent = PlotHelper.GetAlignedExtent(options.AlignedExtentOptions, new CoordinateRect(0, dataWidth, 0, dataHeight));
            this._axisLimits = new AxisLimits(this._alignedExtent);

            this._moveOrientation = moveOrientation;
            this._options = options;
            this._getStepFunc = getStepFunc;
        }


        #region IPlottable

        private bool _isVisible = true;
        /// <summary>
        /// 是否可见
        /// </summary>
        public bool IsVisible
        {
            get { return this._isVisible; }
            set { this._isVisible = value; }
        }


        private IAxes _axes = new Axes();
        /// <summary>
        /// 获取或设置坐标系
        /// </summary>
        public IAxes Axes
        {
            get { return this._axes; }
            set { this._axes = value; }
        }

        private AxisLimits _axisLimits;
        /// <summary>
        /// 获取坐标范围
        /// </summary>
        /// <returns></returns>
        public AxisLimits GetAxisLimits()
        {
            return this._axisLimits;
        }

        /// <summary>
        /// 渲染图
        /// </summary>
        /// <param name="rp"></param>
        public void Render(RenderPack rp)
        {
            if (!this._options.Enable)
            {
                return;
            }

            SKRect rect = this._axes.GetPixelRect(this._alignedExtent).ToSKRect();

            if (this._moveOrientation != PlotMoveDirection.None)
            {
                int step = this._getStepFunc();
                if (step <= 0)
                {
                    if (this._lastDrawTarget == PlotLastDrawTarget.Bitmap2)
                    {
                        rp.Canvas.DrawBitmap(this._bitmap2, rect);
                    }
                    else
                    {
                        rp.Canvas.DrawBitmap(this._bitmap1, rect);
                    }
                }
                else
                {
                    if (this._lastDrawTarget == PlotLastDrawTarget.Bitmap2)
                    {
                        this.Move(rp, rect, this._bitmap2, this._bitmap1, this._bitmap1Canvas, step);
                        this._lastDrawTarget = PlotLastDrawTarget.Bitmap1;
                    }
                    else
                    {
                        this.Move(rp, rect, this._bitmap1, this._bitmap2, this._bitmap2Canvas, step);
                        this._lastDrawTarget = PlotLastDrawTarget.Bitmap2;
                    }
                }
            }
            else
            {
                rp.Canvas.DrawBitmap(this._bitmap1, rect, this._paint);
            }
        }

        private void Move(RenderPack rp, SKRect plotDestRect, SKBitmap lastBitmap,
           SKBitmap currentBitmap, SKCanvas currentCanvas, int step)
        {
            int moveSize;
            SKRect sourceRect, destRect;
            currentCanvas.Clear();

            switch (this._moveOrientation)
            {
                case PlotMoveDirection.Up:
                    moveSize = lastBitmap.Height - step;
                    sourceRect = new SKRect(0, step, lastBitmap.Width, lastBitmap.Height);
                    destRect = new SKRect(0, 0, lastBitmap.Width, moveSize);
                    currentCanvas.DrawBitmap(lastBitmap, sourceRect, destRect);
                    break;
                case PlotMoveDirection.Down:
                    moveSize = lastBitmap.Height - step;
                    sourceRect = new SKRect(0, 0, lastBitmap.Width, moveSize);
                    destRect = new SKRect(0, step, lastBitmap.Width, lastBitmap.Height);
                    currentCanvas.DrawBitmap(lastBitmap, sourceRect, destRect);
                    break;
                case PlotMoveDirection.Left:
                    moveSize = lastBitmap.Width - step;
                    sourceRect = new SKRect(step, 0, lastBitmap.Width, lastBitmap.Height);
                    destRect = new SKRect(0, 0, moveSize, lastBitmap.Height);
                    currentCanvas.DrawBitmap(lastBitmap, sourceRect, destRect);
                    break;
                case PlotMoveDirection.Right:
                    moveSize = lastBitmap.Width - step;
                    sourceRect = new SKRect(0, 0, moveSize, lastBitmap.Height);
                    destRect = new SKRect(step, 0, lastBitmap.Width, lastBitmap.Height);
                    currentCanvas.DrawBitmap(lastBitmap, sourceRect, destRect);
                    break;
                default:
                    throw new NotSupportedException($"不支持的移动方向{this._moveOrientation.ToString()}");
            }

            currentCanvas.Flush();
            rp.Canvas.DrawBitmap(currentBitmap, plotDestRect);
        }

        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<LegendItem> LegendItems
        {
            get { return Enumerable.Empty<LegendItem>(); }
        }

        #endregion


        #region 添加矩形标记

        private SKCanvas GetCanvas()
        {
            if (this._lastDrawTarget == PlotLastDrawTarget.Bitmap2)
            {
                return this._bitmap2Canvas;
            }
            else
            {
                return this._bitmap1Canvas;
            }
        }

        /// <summary>
        /// 添加矩形标记
        /// </summary>
        /// <param name="marker">矩形标记</param>
        public void AddMarker(RectangleMarker marker)
        {
            if (!this._options.Enable || marker == null)
            {
                return;
            }

            if (this._moveOrientation != PlotMoveDirection.None)
            {
                SKCanvas canvas = this.GetCanvas();
                canvas.DrawRect(marker.X, marker.Y, marker.Width, marker.Height, this._paint);
                canvas.Flush();
            }
            else
            {
                this._bitmap1Canvas.DrawRect(marker.X, marker.Y, marker.Width, marker.Height, this._paint);
                this._bitmap1Canvas.Flush();
                this._rectangleMarkerList.Add(marker);
            }
        }

        /// <summary>
        /// 添加标记
        /// </summary>
        /// <param name="markers">标记集合</param>
        public void AddMarkers(IEnumerable<RectangleMarker> markers)
        {
            if (!this._options.Enable || markers == null)
            {
                return;
            }

            if (this._moveOrientation != PlotMoveDirection.None)
            {
                SKCanvas canvas = this.GetCanvas();
                foreach (var marker in markers)
                {
                    if (marker == null)
                    {
                        continue;
                    }

                    canvas.DrawRect(marker.X, marker.Y, marker.Width, marker.Height, this._paint);
                }
                canvas.Flush();
            }
            else
            {
                foreach (var marker in markers)
                {
                    if (marker == null)
                    {
                        continue;
                    }

                    this._bitmap1Canvas.DrawRect(marker.X, marker.Y, marker.Width, marker.Height, this._paint);
                    this._rectangleMarkerList.Add(marker);
                }
                this._bitmap1Canvas.Flush();
            }
        }

        /// <summary>
        /// 移除矩形标记
        /// </summary>
        /// <param name="marker"></param>
        public void RemoveMarker(RectangleMarker marker)
        {
            if (this._moveOrientation != PlotMoveDirection.None)
            {
                throw new InvalidOperationException("图可移动时,不支持移除标注操作,可清空..");
            }

            if (this._rectangleMarkerList.Remove(marker))
            {
                this._bitmap1Canvas.Clear();

                foreach (var marker2 in this._rectangleMarkerList)
                {
                    this._bitmap1Canvas.DrawRect(marker2.X, marker2.Y, marker2.Width, marker2.Height, this._paint);
                }
                this._bitmap1Canvas.Flush();
            }
        }

        /// <summary>
        /// 清空矩形标记
        /// </summary>
        public void ClearMarker()
        {
            this._bitmap1Canvas?.Clear();
            this._bitmap1Canvas?.Flush();

            this._bitmap2Canvas?.Clear();
            this._bitmap2Canvas?.Flush();

            this._rectangleMarkerList?.Clear();
        }

        #endregion

    }

    /// <summary>
    /// 矩形图选项参数
    /// </summary>
    public class RectanglePlotOptions
    {
        /// <summary>
        /// 默认可选项参数
        /// </summary>
        public static readonly RectanglePlotOptions Default = null;

        static RectanglePlotOptions()
        {
            Default = new RectanglePlotOptions();
        }


        /// <summary>
        /// 构造函数初始化
        /// </summary>
        public RectanglePlotOptions()
        {

        }


        /// <summary>
        /// 是否启用时间标注[true:启用;false:禁用]
        /// </summary>
        public bool Enable { get; set; } = true;

        /// <summary>
        /// 对齐选项参数
        /// </summary>
        public PlotAlignedExtentOptions AlignedExtentOptions { get; set; } = null;

        /// <summary>
        /// If true, pixels in the final image will be interpolated to give the heatmap a smooth appearance.
        /// If false, the heatmap will appear as individual rectangles with sharp edges.
        /// </summary>
        public bool Smooth { get; set; } = false;

        /// <summary>
        /// 矩形标记框边框粗细
        /// </summary>
        public float StrokeWidth { get; set; } = 1.0f;

        /// <summary>
        /// 矩形标记框边框颜色
        /// </summary>
        public SKColor StrokeColor { get; set; } = SKColors.Green;

        ///// <summary>
        ///// 是否允许选中
        ///// </summary>
        //public bool AllowSelected { get; set; } = false;

        ///// <summary>
        ///// 选中矩形边框粗细
        ///// </summary>
        //public float SelectedStrokeWidth { get; set; } = 2.0f;

        ///// <summary>
        ///// 选中矩形边框颜色
        ///// </summary>
        //public SKColor SelectedStrokeColor { get; set; } = SKColors.Red;
    }

    /// <summary>
    /// Plot标记基类
    /// </summary>
    public class RectangleMarker
    {
        /// <summary>
        /// 矩形标记x坐标
        /// </summary>
        public readonly float X;

        /// <summary>
        /// 矩形标记y坐标
        /// </summary>
        public readonly float Y;

        /// <summary>
        /// 矩形宽带
        /// </summary>
        public readonly float Width;

        /// <summary>
        /// 矩形高度
        /// </summary>
        public readonly float Height;

        /// <summary>
        /// 标记数据
        /// </summary>
        public object Tag { get; set; } = null;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="x">矩形标记x坐标</param>
        /// <param name="y">矩形标记y坐标</param>
        /// <param name="width">标记矩形宽度</param>
        /// <param name="height">标记矩形高度</param>
        public RectangleMarker(float x, float y, float width, float height)
        {
            this.X = x;
            this.Y = y;
            this.Width = width;
            this.Height = height;
        }
    }
}
